﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using Microsoft.ResourceManagement.ObjectModel;

namespace Quest.FIMPowerShellSnapin
{
    /// <summary>
    /// The FIMPSCmdletResource class represents a FIM resource object as a PowerShell object. It contains a reference to the RmResource object
    /// as well as a reference to the FIMPSCmdletSession that the RmResource object is associate with.
    /// </summary>
    public class FIMPSResource
    {
        private readonly RmResource _resource;
        private readonly FIMPSSession _session;
        private RmResourceChanges _changes;

        public FIMPSResource(FIMPSSession session)
            : this(new RmResource(), session)
        {
        }

        internal FIMPSResource(RmResource obj, FIMPSSession session)
        {
            if (session == null)
            {
                throw new ArgumentNullException();
            }

            _resource = obj;
            _session = session;
            _changes = new RmResourceChanges(_resource);

            _changes.BeginChanges(); // Copies current attribute values so we can calculate changeset later
        }

        /// <summary>
        /// Determines if two FIM PS cmdlet resource objects are equal. To be equal, they must be from the same FIM server, the
        /// credentials used to obtain the object must be the same, and the property values must all be the same.
        /// </summary>
        /// <param name="obj">The PS object to compare</param>
        /// <returns>true if the two objects are equal, false if they are not equal</returns>
        public override bool Equals(object obj)
        {
            if (obj == this)
            {
                return true;
            }

            FIMPSResource other = obj as FIMPSResource;
            if (other == null)
            {
                return false;
            }

            if (Session.Server.ToLowerInvariant() != other.Session.Server.ToLowerInvariant())
            {
                return false;
            }

            if (Session.Credential.UserName.Trim().ToLowerInvariant() != other.Session.Credential.UserName.Trim().ToLowerInvariant())
            {
                return false;
            }

            return Resource.Equals(other.Resource);
        }

        public override int GetHashCode()
        {
            return _resource.GetHashCode();
        }


        public RmResource Resource
        {
            get
            {
                return _resource;
            }
        }

        public FIMPSSession Session
        {
            get
            {
                return _session;
            }
        }

        /// <summary>
        /// Indexer. Allows PoSh to retrieve attributes from the object using indexed array notation. Attribute values are 
        /// returned as strings, except for attributes defined in the schema as dates, which are returned as DateTime.
        /// Multi-valued attributes are returned as a List of either strings or DateTimes.
        /// </summary>
        /// <param name="attributeName">The name of the attribute to retrieve.</param>
        /// <returns></returns>
        public object this[string attributeName]
        {
            get
            {
                object pSValue;

                try // or else PoSh suppresses exceptions 
                {
                    RmAttributeName resourceAttributeName = new RmAttributeName(attributeName);
                    RmAttributeValue resourceValue = _resource[resourceAttributeName];

                    bool isDateTime = (_session.Client.Schema.GetAttributeType(attributeName) == SchemaCache.AttributeType.DateTime);

                    if (_session.Client.Schema.IsMultiValued(resourceAttributeName))
                    {
                        List<object> listOfValues = new List<object>(resourceValue.Values.Count);
                        foreach (RmAttributeValue rmValue in resourceValue.Values)
                        {
                            listOfValues.Add(isDateTime ? ConvertValueToDateTime(rmValue) : rmValue);
                        }
                        pSValue = new ReadOnlyCollection<object>(listOfValues);
                    }
                    else
                    {
                        pSValue = isDateTime ? ConvertValueToDateTime(resourceValue) : resourceValue;
                    }
                }
                catch
                {
                    throw;
                }
                return pSValue;
            }
        }

        private object ConvertValueToDateTime(RmAttributeValue rmValue)
        {
            if (rmValue.IsNullValue())
            {
                return new DateTime();
            }
            else
            {
                return DateTime.Parse(rmValue.ToString());
            }
        }

#if false
        public ICollection<ILMAttributeInfo> Attributes
        {
            get
            {
                List<retList = new List<ILMAttributeInfo>();

                foreach (String key in RmObject.AttributeNames)
                {
                    retList.Add(new ILMAttributeInfo(RmObject[key]));
                }

                return new ReadOnlyCollection<ILMAttributeInfo>(retList);
            }
        }
#endif
        /// <summary>
        /// Adds a value to the specified attribute. If the attribute doesn't exist, it will be created. If the
        /// attribute is single-valued, it throws an exception. If the attribute is multi-valued the specified value 
        /// will be added to the attribute. Note that this code does not check for the prior existence of the
        /// value, so it will be possible to create a multi-valued attribute with multiple identical values, which
        /// would be rejected by the FIM service.
        /// </summary>
        /// <param name="attributeName">The name of the attribute to modify</param>
        /// <param name="attributeValue">The value to set or add to the attribute</param>
        public void AddValue(string attributeName, IComparable attributeValue)
        {
            RmAttributeName rmAttrName = new RmAttributeName(attributeName);

            bool isMultivalued = _session.Client.Schema.IsMultiValued(rmAttrName);
            if(!isMultivalued)
            {
                throw new ArgumentException(string.Format(Constants.Messages.Error_IllegalOpForSVA, attributeName));
            }

            RmAttributeValue rmAttrValue = null;
            if(_resource.TryGetValue(rmAttrName, out rmAttrValue))
            {
                rmAttrValue.Values.Add(attributeValue);
            }
            else
            {
                rmAttrValue.Value = attributeValue;
            }
            _resource[rmAttrName] = rmAttrValue;
        }

        /// <summary>
        /// Removes a value from the specified multi-valued attribute. If the attribute does not exist, or if the 
        /// attribute is defined as single-valued, or if the value does not exist in the multi-value, throws an exception.
        /// </summary>
        /// <param name="attributeName">The name of the attribute to remove or modify</param>
        /// <param name="attributeValue">The value to remove from the attribute</param>
        public void RemoveValue(string attributeName, IComparable attributeValue)
        {
            RmAttributeName rmAttrName = new RmAttributeName(attributeName);

            bool isMultivalued = _session.Client.Schema.IsMultiValued(rmAttrName);
            if(!isMultivalued)
            {
                throw new ArgumentException(string.Format(Constants.Messages.Error_IllegalOpForSVA, attributeName));
            }

            //!! Debateable as to whether this should throw an exception if the attr doesn't exist. Could just ignore it.
            RmAttributeValue rmAttrValue = null;
            if(!_resource.TryGetValue(rmAttrName, out rmAttrValue))
            {
                throw new ArgumentException(string.Format(Constants.Messages.Error_AttributeMissing, attributeName));
            }

            rmAttrValue.Values.Remove(attributeValue);
            _resource[rmAttrName] = rmAttrValue;
        }
        
        /// <summary>
        /// Sets the value of the specified attribute to the specified value. If the attribute value does not exist, it
        /// will be added. If the attribute is single valued, any existing value will be replaced. If the attribute
        /// is multi-valued, the value will be replaced with the specified value. Null is a valid value.
        /// </summary>
        /// <param name="attributeName">The name of the attribute to remove or modify</param>
        /// <param name="attributeValue">The value to remove from the attribute</param>
        public void SetValue(string attributeName, object attributeValue)
        {

            RmAttributeName rmAttrName = new RmAttributeName(attributeName);
            RmAttributeValue rmAttrValue = new RmAttributeValue(attributeValue as IComparable);

            _resource[rmAttrName] = rmAttrValue;
        }

        /// <summary>
        /// Retrieves the RmResourceChanges object associated with this object. The RmResourceChanges object contains
        /// the original value of the objects attributes so that a delta can be computed to update the FIM web service.
        /// </summary>
        /// <returns></returns>
        public RmResourceChanges GetTransactionChanges()
        {
            return _changes;
        }

        public void AcceptChanges()
        {
            _changes.AcceptChanges();
        }
    }
}
